package edu.luc.cs.netkernel.udp;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;

import org.ten60.netkernel.layer1.nkf.INKFConvenienceHelper;
import org.ten60.netkernel.layer1.nkf.INKFRequest;
import org.ten60.netkernel.layer1.nkf.INKFResponse;
import org.ten60.netkernel.layer1.nkf.NKFException;
import org.ten60.netkernel.layer1.nkf.impl.NKFTransportImpl;

import com.ten60.netkernel.scheduler.WorkerThreadPool;
import com.ten60.netkernel.urii.IURRepresentation;
import com.ten60.netkernel.urii.aspect.IAspectString;
import com.ten60.netkernel.util.SysLogger;

/**
 * Simple UDP transport set to receive a resource URI via datagram. 
 * Currently returns a string representation of the requested resource.
 * 
 * @author epwest
 *
 */
public class UDPTransport extends NKFTransportImpl {

	private String description="UDPTransport";
	private String serverHost = "127.0.0.1";
	private int port = 8090;
	
	private DatagramSocket socket;
	private WorkerThreadPool threadpool;
	
	public String getDescription() {	
		return description;
	}

	/**
	 * Automatically called by NetKernel when transport initiates.
	 */
	@Override
	protected void startTransport() throws Exception {
		//Open a socket to accept request packets

		//TO-DO: Socket address parameters should be injected by XML config
		SocketAddress server = new InetSocketAddress(serverHost, port);
		socket = new DatagramSocket(server);

		//Spawn a threadpool. Each running instance of the transport will enter the process() method
		ThreadGroup root=getContext().getKernelHelper().getKernel().getRootThreadGroup();
		
		threadpool = new WorkerThreadPool("UDPTransport", 1, root) {	
			/**
			 * Endless loop to listen for packet arrivals.
			 * 
			 * It's necessary to run this loop in it's own thread rather than the parent
			 * thread (startTransport()) so NetKernel can attend to other things. 
			 */
			@Override
			public void process() {	
				try {	
					while(true){
						byte[] receiveData = new byte[1024];
						DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
						
						socket.receive(receivePacket);
						UDPEventHandler(receivePacket);
					}
				}
				catch (Exception e) {	
					SysLogger.log1(SysLogger.WARNING,this, "Error initialising connection to UDP Transport - exception follows...",e.getMessage());
					SysLogger.log(SysLogger.WARNING, this, e.toString());
					
					try {	
						Thread.currentThread();
						Thread.sleep(10000);
					} catch (InterruptedException e2) {;}
				}
			}
		};
		threadpool.start();
	}
	
	/**
	 * Automatically called by NetKernel to terminate the transport.
	 */	
	@Override
	protected void stopTransport() throws Exception {
		socket.close();
		threadpool.stop();
		threadpool.join(1000);
	}
	
	/**
	 * Event handler called when a packet is received.
	 * 
	 * Determines the request URI from the packet. Issues a subrequest
	 * to NetKernel for the representation as a String aspect. Sends the stringrepresentation
	 * as a datagram packet to the requesting host.
	 * @param data
	 */
	private void UDPEventHandler(DatagramPacket data)
	{
	    INKFConvenienceHelper context=this.getContext();
    	
    	String requestURI = new String(data.getData()).trim();
    	requestURI = this.CleanURI(requestURI);
	    
    	//Create and issue the subrequest into a response.
    	INKFRequest subRequest = context.createSubRequest();
	    try {
	    	byte[] responseData;
	    	
	    	subRequest.setURI(requestURI);
	    	IURRepresentation representation = context.issueSubRequest(subRequest);
	     	
	    	//If the representation offers a String aspect create response.
	    	if (representation.hasAspect(IAspectString.class)) {
		    	IAspectString stringResponse = (IAspectString)representation.getAspect(IAspectString.class);
		     	INKFResponse response =	context.createResponseFrom(stringResponse);

				responseData = stringResponse.getString().getBytes();
	     	}
	    	else { //Create error response.
	     		responseData = "The requested resource is not currently accessible by the UDP tranport".getBytes();
	     	}
	    	
	     	//Extract requestor host info and send the representation to them
	    	InetAddress clientHost = data.getAddress();
			int clientPort = data.getPort();
	     	
			DatagramPacket sendPacket = new DatagramPacket(responseData, responseData.length, clientHost, clientPort);		
			
			try {
				socket.send(sendPacket);
			}
			catch (Exception e){
				e.printStackTrace();
			}
	    }
	    catch (NKFException nke){
	    	nke.printStackTrace();
	    }
	}
	
	/**
	 * Helper method to ensure the request URI is correctly formatted.
	 * @param requestURI
	 * @return formatted requestURI
	 */
	private String CleanURI(String requestURI){
		String uriPrefix = "";
		if (!requestURI.startsWith("ffcpl:")) {
			uriPrefix = "ffcpl:";
			
			if (!requestURI.startsWith("/"))
				uriPrefix = uriPrefix + "/";
		}
		
		return uriPrefix + requestURI;
		
	}
}
